﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using TracedReality.RayTracing;
using QiHe.Yaml.Grammar;

namespace TracedReality.Primitive
{
    /// <summary>
    /// A viewpoint looking at a Scene.
    /// </summary>
    public class Camera
    {
        /// <summary>
        /// Position of this Camera.
        /// </summary>
        public Vector position;

        /// <summary>
        /// The direction that is up.
        /// </summary>
        public Vector up;

        /// <summary>
        /// Where this Camera is looking.
        /// </summary>
        public Vector pointOfInterest;
        
        /// <summary>
        /// FOV of this Camera. Radians?
        /// </summary>
        public double angle;

        /// <summary>
        /// Create a Camera at the given point with the specified up direction, point of interest, and angle
        /// </summary>
        /// <param name="p">Position.</param>
        /// <param name="u">Up.</param>
        /// <param name="poi">Point of interest.</param>
        /// <param name="a">Angle.</param>
        public Camera(Vector p, Vector u, Vector poi, double a)
        {
            position = p;
            up = u;
            pointOfInterest = poi;
            angle = a;
        }

        /// <summary>
        /// Generate a Ray looking at the given coordinates.
        /// Usually coordinates are in the range [0, 1].
        /// </summary>
        /// <param name="x">X coordinate.</param>
        /// <param name="y">Y coordinate.</param>
        /// <returns>A Ray from the Camera passing through the proper coordinates.</returns>
        public Ray generateRay(double x, double y)
        {
            Vector scCenter = position + getDirection();
            double scHeight = 2 * Math.Tan(angle / 2.0);
            Vector xAxis = getHorizontal() * scHeight;
            Vector yAxis = getVertical() * scHeight;
            Vector lLeft = scCenter - (0.5 * xAxis) - (0.5 * yAxis);
            Vector scPoint = lLeft + (x * xAxis) + (y * yAxis);
            Vector dir = scPoint - position;
            dir.normalize();

            return new Ray(scPoint, dir);
        }

        /// <summary>
        /// Get a direction Vector to the PoI.
        /// </summary>
        /// <returns>Direction to the PoI.</returns>
        public Vector getDirection()
        {
            Vector dir = pointOfInterest - position;
            dir.normalize();
            return dir;
        }

        /// <summary>
        /// Get a direction Vector for the horizontal direction.
        /// </summary>
        /// <returns>Horizontal direction.</returns>
        public Vector getHorizontal()
        {
            Vector hor = getDirection().cross(up);
            hor.normalize();
            return hor;
        }

        /// <summary>
        /// Get a direction Vector for the vertical direction.
        /// </summary>
        /// <returns>Vertical direction.</returns>
        public Vector getVertical()
        {
            Vector ver = getHorizontal().cross(getDirection());
            ver.normalize();
            return ver;
        }

        public static Camera Parse(MappingEntry entry)
        {
            if (entry == null) { throw new ArgumentNullException("entry"); }

            Mapping map = (Mapping)entry.Value;
            MappingEntry found = map.Find("position");
            if (found == null) { throw new ArgumentException("No position entry"); }
            Vector position = Vector.FromSequence((Sequence)found.Value);
            found = map.Find("up");
            if (found == null) { throw new ArgumentException("No up entry"); }
            Vector up = Vector.FromSequence((Sequence)found.Value);
            found = map.Find("interest");
            if (found == null) { throw new ArgumentException("No interest entry"); }
            Vector interest = Vector.FromSequence((Sequence)found.Value);
            found = map.Find("angle");
            if (found == null) { throw new ArgumentException("No angle entry"); }
            double angle = double.Parse(found.Value.ToString());

            return new Camera(position, up, interest, angle);
        }

        /// <summary>
        /// Get a string representation of this Camera.
        /// </summary>
        /// <returns>A string representing this Camera.</returns>
        public override string ToString()
        {
            return "Camera: { position: " + position + ", up: " + up + ", interest: " + pointOfInterest + ", angle: " + angle + " }";
        }
    }
}
